package com.zone.mybatis.reflection;

import com.zone.mybatis.reflection.invoker.GetFieldInvoker;
import com.zone.mybatis.reflection.invoker.Invoker;
import com.zone.mybatis.reflection.invoker.MethodInvoker;
import com.zone.mybatis.reflection.property.PropertyTokenizer;

import java.lang.reflect.*;
import java.util.Collection;

/**
 * @author: zongzi
 * @description: TODO
 * @date: 2024/1/14
 */
public class MetaClass {
    private Reflector reflector;

    private MetaClass(Class<?> type) {
        this.reflector = Reflector.forClass(type);
    }

    public static MetaClass forClass(Class<?> type) {
        return new MetaClass(type);
    }

    public Class<?> getGetType(String name) {
        // 班级[0].学生.id
        PropertyTokenizer propertyTokenizer = new PropertyTokenizer(name);
        if (propertyTokenizer.hasNext()) {
            Class<?> childrenType = this.getGetType(propertyTokenizer);
            MetaClass metaClass = MetaClass.forClass(childrenType);
            return metaClass.getGetType(propertyTokenizer.getChildren());
        } else {
            return reflector.getGetType(name);
        }
    }

    private Class<?> getGetType(PropertyTokenizer prop) {
        Class<?> getType = reflector.getGetType(prop.getName());
        if (prop.getIndex() != null && Collection.class.isAssignableFrom(getType)) {
            Type type = this.getGenericType(prop.getName());
            if (type instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                if (actualTypeArguments != null && actualTypeArguments.length == 1) {
                    Type actualTypeArgument = actualTypeArguments[0];
                    if (actualTypeArgument instanceof Class) {
                        return (Class<?>) actualTypeArgument;
                    } else if (actualTypeArgument instanceof ParameterizedType) {
                        return (Class<?>) ((ParameterizedType) actualTypeArgument).getRawType();
                    }
                }
            }
        }
        return getType;
    }

    private Type getGenericType(String name) {
        try {
            Invoker invoker = reflector.getGetInvoker(name);
            if (invoker instanceof MethodInvoker) {
                MethodInvoker methodInvoker = (MethodInvoker) invoker;
                Field _method = methodInvoker.getClass().getDeclaredField("method");
                _method.setAccessible(true);
                Method method = (Method) _method.get(methodInvoker);
                return method.getGenericReturnType();
            } else if (invoker instanceof GetFieldInvoker) {
                GetFieldInvoker fieldInvoker = (GetFieldInvoker) invoker;
                Field field = fieldInvoker.getClass().getDeclaredField("field");
                field.setAccessible(true);
                return field.getGenericType();
            }
        } catch (NoSuchFieldException | IllegalAccessException ignored) {
            throw new RuntimeException(ignored);
        }
        return null;
    }

    public Class<?> getSetType(String name) {
        // 班级[0].学生.id
        PropertyTokenizer propertyTokenizer = new PropertyTokenizer(name);
        if (propertyTokenizer.hasNext()) {
            Class<?> childrenType = reflector.getSetType(propertyTokenizer.getName());
            MetaClass metaClass = MetaClass.forClass(childrenType);
            return metaClass.getSetType(propertyTokenizer.getChildren());
        } else {
            return reflector.getSetType(name);
        }
    }

    public Constructor<?> getDefaultConstructor() {
        return reflector.getDefaultConstructor();
    }

    public Invoker getGetInvoker(String propertyName) {
        return reflector.getGetInvoker(propertyName);
    }

    public Class<?> getType() {
        return this.reflector.getType();
    }

    public Invoker getSetInvoker(String propertyName) {
        return reflector.getSetInvoker(propertyName);
    }

    public boolean hasSetter(String name) {
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
            Class<?> clazz = this.getGetType(prop);
            MetaClass metaClass = MetaClass.forClass(clazz);
            return metaClass.hasSetter(prop.getChildren());
        } else {
            return reflector.hasSetter(name);
        }
    }
}
